﻿using Android.App;
using Android.Content;
using Android.Media;
using AndroidX.ConstraintLayout.Helper.Widget;
using AndroidX.Core.App;
using InteractiveMusicPlayerMobile.Platforms.Android.音频播放;
using InteractiveMusicPlayerMobile.控件与界面;
using NAudio.Wave;
using System.Diagnostics;
using System.Net.Sockets;
using 交互音乐播放器.中间件;
using 交互音乐播放器.效果器;
using 交互音乐播放器.数据;
using 交互音乐播放器.数据.播放中数据;
using 交互音乐播放器.音频逻辑;

namespace InteractiveMusicPlayerMobile.音频播放
{
    [Service]
    public class 安卓音频播放 : Service, 音频控制器接口
    {
        public static 音频控制器接口? 当前控制器;
        #region 公用字段与属性
        public static 音乐播放数据 数据;
        public static 脚本文件数据 脚本;
        /// <summary>
        /// 已经创建的播放器组，使用播放流的别名标记
        /// </summary>
        public Dictionary<string, 播放流> 音频播放流组 = new();
        public Dictionary<string, IWaveProvider> 读取器集 = new();
        public Dictionary<string, WaveStream> 已载文件组 { get; set; } = new();
        #region 平台字段与属性
        public int 最小缓冲区 = 0;
        public bool 停止输出 = false;
        private string 当前播放流名称 = "";
        /// <summary>
        /// 处理用户待执行的消息
        /// </summary>
        public List<Action> 用户消息队列 = new List<Action>();
        #endregion
        #endregion
        #region 服务代码（安卓）
        private string NOTIFICATION_CHANNEL_ID = "1000";
        private int NOTIFICATION_ID = 1;
        private string NOTIFICATION_CHANNEL_NAME = "notification";
        public static Intent 服务 { get; set; }
        public override void OnCreate()
        {
            base.OnCreate();
        }


        public override Android.OS.IBinder OnBind(Intent intent)
        {
            return null;
        }
        private void startForegroundService()
        {
            var notifcationManager = GetSystemService(Context.NotificationService) as NotificationManager;

            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
            {
                createNotificationChannel(notifcationManager);
            }

            var notification = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
            notification.SetAutoCancel(false);
            notification.SetOngoing(true);
            notification.SetSmallIcon(Resource.Mipmap.appicon);
            notification.SetContentTitle("交互音乐播放器 2");
            notification.SetContentText("正在后台循环播放音乐");
            StartForeground(NOTIFICATION_ID, notification.Build());
        }
        private void createNotificationChannel(NotificationManager notificationMnaManager)
        {
            var channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME,
            NotificationImportance.Low);
            notificationMnaManager.CreateNotificationChannel(channel);
        }
        public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
        {
            startForegroundService();
            return StartCommandResult.NotSticky;
        }

        #endregion
        #region 平台代码（安卓适配）
        private (int 最小缓冲区, int 采样率, ChannelOut 声道类型, Android.Media.Encoding 编码格式) 测算最小缓冲区()
        {
            Debug.WriteLine("当前已载文件组数量为 " + 已载文件组.Count);
            var 采样率 = 已载文件组.FirstOrDefault().Value.WaveFormat.SampleRate;
            var 声道数 = ChannelOut.Stereo;
            var 格式 = Android.Media.Encoding.Pcm16bit;
            最小缓冲区 = AudioTrack.GetMinBufferSize(采样率, 声道数, 格式);
            if (已载文件组.Count == 1) { 最小缓冲区 *= 2; return (最小缓冲区, 采样率, 声道数, 格式); }
            //寻找出本音频轨中，音频的最大大小，进行算法优化
            long 当前最大值 = 0;
            foreach (var 文件流 in 已载文件组.Values)
            {
                if (文件流.Length > 当前最大值) { 当前最大值 = 文件流.Length; }
            }
            var 倍率 = (int)(当前最大值 / (long)4832348);
            if (倍率 < 121) { 倍率 = 121; }
            Console.WriteLine($"倍率为{倍率}");
            最小缓冲区 *= 倍率;
            return (最小缓冲区, 采样率, 声道数, 格式);
        }
        public static void 启动播放服务()
        {
        
            //服务启动相关
            if (!播放信息.运行时.服务已启动)
            {
                播放信息.运行时.服务已启动 = true;
                Android.Content.Intent intent = new Android.Content.Intent(Android.App.Application.Context, typeof(安卓音频播放));
                安卓音频播放.服务 = intent;
                Android.App.Application.Context.StartForegroundService(intent);
              
            }
          
        }
        #endregion
        #region 构造函数
        public 安卓音频播放(脚本文件数据 脚本数据, 音乐播放数据 数据)
        {
            脚本 = 脚本数据;
            安卓音频播放.数据 = 数据;
            当前控制器 = this;
            Debug.WriteLine(脚本数据.格式);
        }
        public 安卓音频播放()
        {
            new Thread(() =>
            {
                while (true)
                {
                  
                    Thread.Sleep(1500);
                }

            }).Start();
        }
        #endregion
        #region 初始化音频类命令
        /// <summary>
        /// 已经载入的文件组，使用文件的别名标记
        /// </summary>
        public bool 命令_加载文件(string 文件路径, string 文件别名)
        {
            Debug.WriteLine($"脚本的格式为{脚本.格式},文件类型为{系统控制.支援的文件类型.ogg.ToString()}");
            if (脚本.格式 == 系统控制.支援的文件类型.ogg.ToString())
            {
                if (!System.IO.File.Exists(文件路径)) { Debug.Print("配置文件中的路径可能有问题，请手动修改配置文件中的路径"); return false; }
                var 解析结果 = 辅助类.音频解析.解析Ogg文件(文件路径);

                已载文件组加入文件(文件别名, 文件路径, 解析结果.文件流, 解析结果.读取器);
                if (脚本.BPM组 != null)
                {
                    命令_添加文件节拍信息(文件别名, 数据.段落组[文件别名].节拍信息_读取.BPM, 解析结果.文件流.Length, 解析结果.文件流.TotalTime);
                    命令_添加文件属性信息(文件别名, 文件路径);
                }
                return true;
            }
            return false;
        }

        public bool 已载文件组加入文件(string 文件别名, string 文件路径, WaveStream 文件流, IWaveProvider 读取器)
        {
            //如果已在文件组里，就更换到新的文件文件
            if (已载文件组.ContainsKey(文件别名)) { 已载文件组[文件别名] = 文件流; }
            else { 已载文件组.Add(文件别名, 文件流); }
            //如果已在读取器组里，就更换到新的文件文件
            if (读取器集.ContainsKey(文件别名)) { 读取器集[文件别名] = 读取器; }
            else { 读取器集.Add(文件别名, 读取器); }

            if (数据.段落组.ContainsKey(文件别名))
            {
                if (数据.段落组[文件别名].绑定文件 == null)
                { 数据.段落组[文件别名].绑定文件 = new 文件信息() { 别名 = 文件别名, 文件实例 = 文件流, 路径 = 文件路径 }; }
                else
                { 数据.段落组[文件别名].绑定文件!.文件实例 = 文件流; }
            }
            else
            {
                数据.段落组.Add(文件别名, new 段落信息()
                {
                    别名 = 文件别名,
                    绑定文件 = new 文件信息 { 别名 = 文件别名, 文件实例 = 文件流, 路径 = 文件路径 },
                });
            }
            return true;
        }

        public void 命令_添加文件属性信息(string 文件别名, string 文件路径)
        {
            var 段落访问序号 = 脚本.段落名称.IndexOf(文件别名);
            var 段落真实编号 = 脚本.文件组.IndexOf(文件路径);
            if (段落访问序号 != -1)
            {
                数据.段落组[文件别名].Offset = 脚本.Offset组[段落访问序号];
                数据.段落组[文件别名].启用循环 = 脚本.循环下标组[段落访问序号];
                数据.段落组[文件别名].文件编号 = 段落真实编号;
            }
            else //如果段落是用户执行脚本过程中自行建立的，则应用默认的数据
            {

                数据.段落组[文件别名].Offset = 脚本.Offset组[段落真实编号];
                数据.段落组[文件别名].启用循环 = 脚本.循环下标组[段落真实编号];
                数据.段落组[文件别名].文件编号 = 段落真实编号;
                //数据.段落组[文件别名].启用循环 = false;
            }
        }

        public void 命令_添加文件节拍信息(string 文件别名, float BPM, long 文件大小, TimeSpan 播放总时长)
        {
            int 段落访问序号 = -1;
            if (脚本.段落名称.IndexOf(文件别名) != -1)
            {
                段落访问序号 = 脚本.段落名称.IndexOf(文件别名);
            }

            var 每拍字节 = 数据转换.计算每拍字节(BPM, 播放总时长, 文件大小);
            数据.段落组[文件别名].节拍信息_读取.BPM = BPM;
            数据.段落组[文件别名].节拍信息_读取.每拍字节 = 每拍字节;
            if (段落访问序号 != -1) //如果这个段落是用户自行建立，而并非脚本配置中带有的，则应用默认的数值
            {
                数据.段落组[文件别名].节拍信息_读取.每小节拍数 = 脚本.小节节拍总数组[段落访问序号];
                数据.段落组[文件别名].节拍信息_显示.每小节拍数 = 脚本.小节节拍总数组[段落访问序号];
            }
            else
            {
                数据.段落组[文件别名].节拍信息_读取.每小节拍数 = 脚本.默认小节节拍总数;
                数据.段落组[文件别名].节拍信息_显示.每小节拍数 = 脚本.默认小节节拍总数;
            }
            数据.段落组[文件别名].节拍信息_显示.BPM = BPM;
            数据.段落组[文件别名].节拍信息_显示.每拍字节 = 每拍字节;

            数据.段落组[文件别名].播放位置.总字节 = 文件大小;
            数据.段落组[文件别名].播放位置.总时间 = 播放总时长;
            数据.段落组[文件别名].播放位置.剩余时间 = 播放总时长;
            数据.段落组[文件别名].播放位置.剩余字节 = 文件大小;
        }

        public int 命令_新建播放流(string 流别名, int 流编号)
        {
            var 信息 = 测算最小缓冲区();

            var 播放流 = new 播放流(Android.Media.Stream.Music, 信息.采样率, 信息.声道类型, 信息.编码格式, 信息.最小缓冲区, AudioTrackMode.Stream);
            播放流.流编号 = 流编号;
            var 音频播放器 = 播放流.流;
            音频播放流组.Add(流别名, 播放流);
            播放流组加入流(流别名, 播放流, 流编号, 音频播放器);
            return 流编号;
        }

        public bool 播放流组加入流(string 流别名, 播放流 播放流, int 流编号, AudioTrack 播放器实例)
        {
            if (音频播放流组.ContainsKey(流别名)) { 音频播放流组[流别名] = 播放流; }
            else { 音频播放流组.Add(流别名, 播放流); }
            if (数据.播放流组.ContainsKey(流别名))
            {
                数据.播放流组[流别名] = new 流信息()
                {
                    流别名 = 流别名,
                    流实例 = 播放流,
                    播放器实例 = 播放器实例,
                    流编号 = 流编号
                };
                播放流.流编号 = 流编号; 播放流.流别名 = 流别名;
            }
            else
            {
                数据.播放流组.Add(流别名, new 流信息()
                {
                    流别名 = 流别名,
                    流实例 = 播放流,
                    播放器实例 = 播放器实例,
                    流编号 = 流编号
                });
                播放流.流编号 = 流编号; 播放流.流别名 = 流别名;
            }
            return true;
        }

        public bool 命令_播放音乐(string 播放流名称, string 文件别名)
        {

            if (音频播放流组[播放流名称].WaveFormat == null)
            {
                音频播放流组[播放流名称].设置音频格式(已载文件组[文件别名].WaveFormat);
            }
            //音频播放器组[播放流名称].Init(音频播放流组[播放流名称]); //该平台不需要初始化
            ///BUG 更换文件的时候因为未指定播放流，所以出现问题
            命令_更换文件(文件别名, 播放流名称, -1);
            if (数据.播放流组.ContainsKey(播放流名称) && 数据.段落组.ContainsKey(文件别名) && 数据.段落组[文件别名] != null &&
                数据.播放流组[播放流名称].当前段落 != null)
            {
                数据.播放流组[播放流名称].当前段落 = 数据.段落组[文件别名];
                if (数据.文件组.ContainsKey(文件别名))
                {
                    if (数据.播放流组[播放流名称].当前段落!.绑定文件.文件实例 == null)
                    {
                        数据.播放流组[播放流名称].当前段落!.绑定文件 = 数据.文件组[文件别名];
                    }

                }


                数据.播放流组[播放流名称].当前段落!.状态 = 段落信息.播放状态.播放中;
                Task.Run(() =>
                {
               
                        if (服务 == null)
                        {
                        OnCreate();

                        }
                        读取文件流(播放流名称);

                        当前播放流名称 = 播放流名称;
                  

                    
       
                });
       

            }
            else { Debug.Print("播放失败，因为数据信息不完整"); return false; }
            return true;
        }

        private void 读取文件流(string 播放流名称)
        {

            try
            {
                数据.重定向当前流引用(播放流名称);
                Debug.WriteLine($"{播放流名称} 的 循环开始了");
                //先放在读数据，不然稍后读的状态可能为停止
                音频播放流组[播放流名称].流.Play();
                while (数据.播放流组[播放流名称].当前段落!.状态 == 段落信息.播放状态.播放中)
                {
                    byte[] 音频缓冲区 = new byte[最小缓冲区];
                    内部_取得播放数据(音频缓冲区, 0, 最小缓冲区, 音频播放流组[播放流名称].流别名);
                    Debug.WriteLine($"{播放流名称} 的 循环一次完毕");

                }
            }
            catch (Exception ex)
            {
                通知与日志.向当前页弹窗($"该异常位于“流：{播放流名称}”播放的“{数据.播放流组[播放流名称].当前段落.绑定文件.路径}”的“{数据.播放流组[播放流名称].当前段落.别名}”段落，详细：\r\n{ex.Message}\r\n堆栈：\r\n{ex.StackTrace}", "播放线程异常");

                throw ex;
            }
            Debug.Print("已停止读取文件流");
        }

        #endregion
        #region 播放时命令
        /// <summary>
        /// 内部取得播放数据的方法，该方法目前的问题在“流别名”、“当前文件”可能有冲突情况
        /// </summary>
        /// <param name="音频缓冲区"></param>
        /// <param name="偏移值"></param>
        /// <param name="读取大小"></param>
        /// <param name="流别名"></param>
        /// <returns></returns>
        public int 内部_取得播放数据(byte[] 音频缓冲区, int 偏移值, int 读取大小, string 流别名)
        {
            Debug.WriteLine($"{流别名}");

            int 实际读取字节 = 0; //实际读取字节数
            //CUE点处理，若处理完毕，则返回已经处理的字节数量，然后交回本方法处理剩余的字节
            if (数据.播放流组[流别名].当前段落!.两点循环Cue != null) //是否已到两点循环位置
            {
                if (数据.播放流组[流别名].当前段落!.播放位置.已播放字节 + 音频缓冲区.Length > 数据.播放流组[流别名].当前段落!.两点循环Cue!.出点位置)
                {
                    var 处理结果 = 内部_处理CUE点逻辑(音频缓冲区, 偏移值, 读取大小, 流别名); //该方法内部有音频流处理、读取逻辑
                    if (处理结果.是否继续处理 == true)
                    {
                        读取大小 = 音频缓冲区.Length - 偏移值;
                        实际读取字节 = 处理结果.已处理字节;

                    }
                }
            }
            if (数据.播放流组[流别名].当前段落!.下个Cue点 != null)
            {
                if (数据.播放流组[流别名].当前段落!.播放位置.已播放字节 + 音频缓冲区.Length > 数据.播放流组[流别名].当前段落!.下个Cue点!.出点位置) //是否已到下个Cue点
                {
                    var 处理结果 = 内部_处理CUE点逻辑(音频缓冲区, 偏移值, 读取大小, 流别名); //该方法内部有音频流处理、读取逻辑
                    if (处理结果.是否继续处理 == true)
                    {
                        读取大小 = 音频缓冲区.Length - 偏移值;
                        实际读取字节 = 处理结果.已处理字节;
                        //偏差位置
                    }
                }
            }
            //循环逻辑，若文件剩余大小已不足以完成本次读取，则先更换流，然后把读取器的位置设为0
            var 当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
            /*
            Debug.WriteLine($"{已载文件组[当前文件].Position}/{已载文件组[当前文件].Length} < {最小缓冲区 * 2}");
            */
            if (已载文件组[当前文件].Length - 已载文件组[当前文件].Position <= 读取大小 - 实际读取字节)
            {

                var 最后的字节 = 读取器集[当前文件].Read(音频缓冲区, 偏移值 + 实际读取字节, (int)(已载文件组[当前文件].Length - 已载文件组[当前文件].Position) / 2);
                实际读取字节 += 音频播放流组[流别名].流.Write(音频缓冲区, 偏移值 + 实际读取字节, (int)(最后的字节) );//此处的从头开始还需要斟酌
                /*
                Debug.WriteLine($"读到最后 {实际读取字节}/{最后的字节}");
                Debug.WriteLine($"更换流 {已载文件组[当前文件].Position}/{已载文件组[当前文件].Length} < {最小缓冲区 * 2}");
                Debug.WriteLine($"> 总：{音频缓冲区.Length} {偏移值 + 实际读取字节} -> {读取大小 - 实际读取字节} = {偏移值 + 实际读取字节 + (读取大小 - 实际读取字节)}");
                */
                本段落读取完毕(流别名);//事件，如果本段落读取完毕要做的操作


                当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
                /*
                Debug.WriteLine($"已换流 {已载文件组[当前文件].Position}/{已载文件组[当前文件].Length} < {读取大小 - 实际读取字节}");
                Debug.WriteLine($"> 总：{音频缓冲区.Length} {偏移值 + 实际读取字节} -> {读取大小 - 实际读取字节} = {偏移值 + 实际读取字节 + (读取大小 - 实际读取字节)}");
                */
                var 局部剩余应读取大小 = (读取大小 - 实际读取字节) / 2;
                //块对齐
                //块对齐是每个样本的字节数乘以通道数。你需要确保你读取的是完整的数据块，而且是块对齐的倍数。例如，如果你的文件有4字节的块对齐，需要一次读取4、8、12等字节。此处可能会因为拖动播放进度，导致进行到不正确的数据块大小，发生错误。
                //小于0为判断是否在重定位文件中已到达更远位置，%4代表是否计算了正确的数据块。
                //对应消除异常： Naudio Must be a multple of channels!
                if (局部剩余应读取大小 % 4 != 0)
                {
                    Debug.WriteLine($"> 已修正块问题");
                    局部剩余应读取大小 -= 局部剩余应读取大小 % 4;
                }

               /* Debug.WriteLine($"> 总：{音频缓冲区.Length} {偏移值 + 实际读取字节} -> {读取大小 - 实际读取字节} 【{最小缓冲区- 读取大小 - 实际读取字节}】 = {偏移值 + 实际读取字节 + (读取大小 - 实际读取字节)}");*/
                读取器集[当前文件].Read(音频缓冲区, 0, 局部剩余应读取大小);
                使用效果(音频缓冲区, 偏移值, 读取大小, 流别名);//先使用效果再写入
                实际读取字节 += 音频播放流组[流别名].流.Write(音频缓冲区, 0, 局部剩余应读取大小);
   
                内部_更新播放状态(实际读取字节, 流别名);
                内部_用户端请求处理();
                return 实际读取字节; //无论如何都要返回已经读取了这么多字节


            }
            //如果刚刚更换了流，这里会把下一章节的数据补充进缓冲区的剩余部分，避免造成空数据
            var 剩余应读取大小 = (读取大小 - 实际读取字节) / 2;
            //块对齐
            //块对齐是每个样本的字节数乘以通道数。你需要确保你读取的是完整的数据块，而且是块对齐的倍数。例如，如果你的文件有4字节的块对齐，需要一次读取4、8、12等字节。此处可能会因为拖动播放进度，导致进行到不正确的数据块大小，发生错误。
            //小于0为判断是否在重定位文件中已到达更远位置，%4代表是否计算了正确的数据块。
            //对应消除异常： Naudio Must be a multple of channels!
            if (剩余应读取大小 % 4 != 0)
            {
                Debug.WriteLine($"> 已修正块问题");
                剩余应读取大小 -= 剩余应读取大小 % 4;
            }

           // Debug.WriteLine($"> 总：{音频缓冲区.Length} {偏移值 + 实际读取字节} -> {读取大小 - 实际读取字节} = {偏移值 + 实际读取字节 + (读取大小 - 实际读取字节)}");
            读取器集[当前文件].Read(音频缓冲区, 偏移值 + 实际读取字节, 剩余应读取大小);
            使用效果(音频缓冲区, 偏移值, 读取大小, 流别名);//先使用效果再写入
            实际读取字节 += 音频播放流组[流别名].流.Write(音频缓冲区, 偏移值 + 实际读取字节, 剩余应读取大小);

            内部_更新播放状态(实际读取字节, 流别名);
            内部_用户端请求处理();
            return 实际读取字节; //无论如何都要返回已经读取了这么多字节
        }

        public enum 请求类型
        {
            暂停,
            停止,
            播放位置调整
        }

        /// <summary>
        /// 在内部按队列处理用户端的请求
        /// </summary>
        private void 内部_用户端请求处理()
        {
            if (用户消息队列.Count == 0) { return; }
            用户消息队列.ForEach(x => { x.Invoke(); });
            用户消息队列.Clear();
        }

        /// <summary>
        /// 当到达某个Cue点时的处理逻辑
        /// </summary>
        /// <param name="音频缓冲区"></param>
        /// <param name="偏移值"></param>
        /// <param name="读取大小"></param>
        /// <param name="流别名"></param>
        /// <returns></returns>
        public (bool 是否继续处理, int 已处理字节) 内部_处理CUE点逻辑(byte[] 音频缓冲区, int 偏移值, int 读取大小, string 流别名)
        {

            if (计划.全局事件.Contains(计划.事件.到达指定CUE点并执行脚本)) //Cue点含有要执行的脚本时
            {
                if (数据.播放流组[流别名].当前段落!.下个Cue点!.绑定的脚本 != null) //如果下个Cue点有绑定脚本，则现在立刻运行该脚本
                {
                    脚本解析器.当前解析.运行脚本(数据.播放流组[流别名].当前段落!.下个Cue点!.绑定的脚本);
                    脚本命令.回执_启用默认按钮();
                }
                数据.播放流组[流别名].当前段落!.下个Cue点 = null;
                计划.全局事件.Remove(计划.事件.到达指定CUE点并执行脚本);
                return (true, 0);
            }
            if (计划.全局事件.Contains(计划.事件.到达指定CUE点并切换)) //Cue点含有切换的模式时
            {
                var 当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
                long 剩余大小 = 数据.播放流组[流别名].当前段落!.下个Cue点!.出点位置 - 数据.播放流组[流别名].当前段落!.播放位置.已播放字节;
                //块对齐代码
                if (剩余大小 % 4 != 0)
                {
                    剩余大小 -= 剩余大小 % 4;
                }
                var 实读大小 = 读取器集[当前文件].Read(音频缓冲区, 偏移值, (int)剩余大小 / 2); //读写闭环
                实读大小 = 音频播放流组[流别名].流.Write(音频缓冲区, 偏移值, (int)剩余大小 / 2); //读写闭环
                数据.播放流组[流别名].当前段落.已通过Cue点读取部分字节 = true;
                数据.播放流组[流别名].当前段落.已通过Cue点读取大小 = 实读大小;
                
                命令_更换文件(数据.播放流组[流别名].当前段落!.下个Cue点!.入点段落, 流别名, (int)数据.播放流组[流别名].当前段落!.下个Cue点!.入点位置); //使用更换文件的方法来达到Cue切换的目的
                脚本命令.回执_启用默认按钮();
                数据.播放流组[流别名].当前段落!.下个Cue点 = null;
                计划.全局事件.Remove(计划.事件.到达指定CUE点并切换);
                return (true, (int)实读大小); //若出问题，此处可能应该除2（双声道）
            }
            if (计划.全局事件.Contains(计划.事件.处理两点循环)) //如果是用做两点循环的Cue点
            {
                var 当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
                long 剩余大小 = 数据.播放流组[流别名].当前段落!.两点循环Cue!.出点位置 - 数据.播放流组[流别名].当前段落!.播放位置.已播放字节;
                //补丁代码，WAV的计算可能不均匀
                if (Path.GetExtension(数据.播放流组[流别名].当前段落!.绑定文件!.路径) == ".wav") //如果是Wav文件则计算方式不太一样（有可能这个格式有和Pcm16相似的特性）
                {
                    //这个算法暂时比较奇葩
                    //除2是双声道，4是字节块大小，若除尽则是有效块
                    while (剩余大小 / 2 % 4 != 0)
                    {
                        剩余大小 += 剩余大小 / 2 % 4 * 2;
                    }

                }
                //块对齐是每个样本的字节数乘以通道数。你需要确保你读取的是完整的数据块，而且是块对齐的倍数。例如，如果你的文件有4字节的块对齐，需要一次读取4、8、12等字节。此处可能会因为拖动播放进度，导致进行到不正确的数据块大小，发生错误。
                //小于0为判断是否在重定位文件中已到达更远位置，%4代表是否计算了正确的数据块。
                剩余大小 /= 2;
                if (剩余大小 < 0 && 剩余大小 % 4 != 0)
                {
                    剩余大小 = 音频缓冲区.Length;
                }
                //块对齐代码，效果同上
                if (剩余大小 % 4 != 0)
                {
                    剩余大小 -= 剩余大小 % 4;
                }
                //Debug.WriteLine($"准备切换 - 当前缓冲区大小{音频缓冲区.Length}，偏移{偏移值}，需读取{剩余大小}");
                var 实读大小 = 读取器集[当前文件].Read(音频缓冲区, 偏移值, (int)剩余大小); //读写闭环
                实读大小 = 音频播放流组[流别名].流.Write(音频缓冲区, 偏移值, (int)剩余大小); //读写闭环
                数据.播放流组[流别名].当前段落.已通过Cue点读取部分字节 = true;
                数据.播放流组[流别名].当前段落.已通过Cue点读取大小 = 实读大小;
                命令_更换文件(数据.播放流组[流别名].当前段落!.两点循环Cue!.入点段落, 流别名, (int)数据.播放流组[流别名].当前段落!.两点循环Cue!.入点位置);
                return (true, (int)实读大小 / 2);//若出问题，此处可能应该除2（双声道）
            }
            if (计划.全局事件.Contains(计划.事件.自动处理所有Cue点)) //如果是全自动处理的
            {
                var 当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
                long 剩余大小 = 数据.播放流组[流别名].当前段落!.下个Cue点!.出点位置 - 数据.播放流组[流别名].当前段落!.播放位置.已播放字节;
                //块对齐代码，效果同上
                if (剩余大小 % 4 != 0)
                {
                    剩余大小 -= 剩余大小 % 4;
                }
                var 实读大小 = 读取器集[当前文件].Read(音频缓冲区, 偏移值, (int)剩余大小 / 2);//读写闭环
                实读大小 = 音频播放流组[流别名].流.Write(音频缓冲区, 偏移值, (int)剩余大小 / 2); //读写闭环
                数据.播放流组[流别名].当前段落.已通过Cue点读取部分字节 = true;
                数据.播放流组[流别名].当前段落.已通过Cue点读取大小 = 实读大小;
                命令_更换文件(数据.播放流组[流别名].当前段落!.下个Cue点!.入点段落, 流别名, (int)数据.播放流组[流别名].当前段落!.下个Cue点!.入点位置);
                脚本命令.回执_启用默认按钮();
                数据.播放流组[流别名].当前段落!.下个Cue点 = null;
                计划.全局事件.Remove(计划.事件.到达指定CUE点并切换);
                return (true, (int)实读大小);
            }

            return (true, 0);
        }

        /// <summary>
        /// 处理内部信息更新
        /// </summary>
        /// <param name="剩余读取字节量"></param>
        /// <param name="流别名"></param>
        public void 内部_更新播放状态(int 剩余读取字节量, string 流别名)
        {
            if (数据.当前流 == null) { return; }
            if (数据.当前流.流别名 == "未设置流别名") { 数据.重定向所有当前引用(); }
            数据.播放流组[流别名].当前段落.缓冲区大小 = 剩余读取字节量;
            var 当前文件别名 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
            var 当前文件 = 已载文件组[当前文件别名];
            数据.播放流组[流别名].当前段落.缓冲区时间 = 数据转换.取缓冲区执行毫秒(剩余读取字节量, 当前文件); //必须先进行公用更新再判断是否为本段，否则无法计算缓冲区导致效果失效
            if (流别名 != 数据.当前流!.流别名) { return; }
            var 当前段落号 = 数据.播放流组[流别名].当前段落!.编号;
            var 当前段落别名 = 数据.播放流组[流别名].当前段落!.别名;
            var 当前段落 = 数据.播放流组[流别名].当前段落;
            var 当前流 = 数据.播放流组[流别名];
            if (当前段落 == null) { return; }
            当前段落.播放位置.已播放字节 = 当前文件.Position;
            当前段落.播放位置.总字节 = 当前文件.Length;
            当前段落.播放位置.当前时间 = 当前文件.CurrentTime;
            当前段落.播放位置.总时间 = 当前文件.TotalTime;
            当前流.段落读取字节数 += 剩余读取字节量;
            if (当前段落.节拍信息_读取!.BPM != -1)
            {
                double 小节偏移 = 0;
                if (脚本.Offset组.Count - 1 >= 当前段落号) { 小节偏移 = 脚本.Offset组[当前段落.文件编号]; }

                var 当前读取总拍数 = (int)((当前文件.Position + 数据转换.数字时间转目标字节(小节偏移, 当前文件)) / 当前段落.节拍信息_读取!.每拍字节);
                当前段落.节拍信息_读取.拍 = 当前读取总拍数 % 当前段落.节拍信息_读取.每小节拍数 + 1;
                当前段落.节拍信息_读取.小节 = 当前读取总拍数 / 当前段落.节拍信息_读取.每小节拍数 + 1;
            }
            //bug
            
            if (停止输出 == false && 音频播放流组.ContainsKey(流别名) && 音频播放流组[流别名].流 != null)
            {
                if (音频播放流组[流别名].流.PlayState == PlayState.Playing) { 当前段落.状态 = 段落信息.播放状态.播放中; }
                if (音频播放流组[流别名].流.PlayState == PlayState.Paused) { 当前段落.状态 = 段落信息.播放状态.暂停; }
                if (音频播放流组[流别名].流.PlayState == PlayState.Stopped) { 当前段落.状态 = 段落信息.播放状态.停止; }
            }
            
            控件与界面.播放信息.运行时.总时间 = $"{当前段落.播放位置.总时间.ToString(@"mm\:ss")}";
            控件与界面.播放信息.运行时.已播时间 = $"{当前段落.播放位置.当前时间.ToString(@"mm\:ss")}";
            控件与界面.播放信息.运行时.时间百分比 = ((int)System.Math.Round(((float)当前段落.播放位置.已播放字节 / (float)当前段落.播放位置.总字节) * 100, 0));
            控件与界面.播放信息.运行时.音频章节名 = 当前段落.别名;
            //Debug.Print($"{流别名} / {当前段落别名} {当前段落.播放位置.当前时间} - {当前段落.播放位置.已播放字节 - 当前段落.缓冲区大小} + {当前段落.缓冲区大小} -> {当前段落.播放位置.已播放字节} / {当前段落.播放位置.总字节} <{当前控制器!.音频播放器组[流别名].GetPosition()} - {当前段落.Offset} ={当前控制器!.音频播放器组[流别名].GetPosition() - 当前段落.Offset}> | {当前段落.节拍信息_读取.小节}.{当前段落.节拍信息_读取.拍}");
            补例_更新播放内容();
        }

        public void 补例_更新播放内容()
        {
            var 播放数据 = 数据;
            var 小节偏移 = 播放数据.当前流.当前段落.Offset;
           var 当前文件 = 音频控制器注册.当前平台音频控制器.已载文件组[播放数据.当前流.当前段落.绑定文件.别名];
            var 当前位置 = 音频控制器注册.当前平台音频控制器.已载文件组[播放数据.当前流.当前段落.绑定文件.别名].Position;
        

            if (计划.全局事件 != null && 计划.全局事件.Contains(计划.事件.播放至指定CUE点并执行脚本))
            {
                if (当前位置 - 播放数据.当前流.播放器偏移 + 数据转换.数字时间转目标字节(((double)UI界面数据.UI动态更新时间 / 1000), (WaveStream)播放数据.当前流.当前段落.绑定文件.文件实例) >= 播放数据.当前流.当前段落.下个Cue点!.出点位置)
                {
                    Debug.Print($"!!!!!!!!!!在{当前位置 - 播放数据.当前流.播放器偏移} - {播放数据.当前流.当前段落.下个Cue点!.出点位置}={当前位置 - 播放数据.当前流.播放器偏移 - 播放数据.当前流.当前段落.下个Cue点!.出点位置}  => 当前播放位置在跨平台时无法获取 | {播放数据.当前流.当前段落.节拍信息_显示.小节}.{播放数据.当前流.当前段落.节拍信息_显示.拍} 节拍处切换");
                    if (播放数据.当前流.当前段落!.下个Cue点!.绑定的脚本 != null)
                    {

                        脚本解析器.当前解析.运行脚本(播放数据.当前流.当前段落!.下个Cue点!.绑定的脚本);
                        脚本命令.回执_启用默认按钮();
                    }
                    播放数据.当前流.当前段落!.下个Cue点 = null;
                    计划.全局事件.Remove(计划.事件.播放至指定CUE点并执行脚本);
                }
            }
            if (计划.全局按钮集.Count > 0 && 计划.全局默认按钮 == null)
            { 计划.全局默认按钮 = 计划.全局按钮集.FirstOrDefault().Value; }
            var 默认按钮 = 计划.全局默认按钮;
        }

        public void 使用效果(byte[] 音频缓冲区, int 偏移值, int 读取大小, string 流别名)
        {
            //if (!数据.播放流组.ContainsKey(流别名)) { return; }
            //if (!脚本.状态_当前段落.ContainsKey(流别名)) { return; }
            var 当前文件 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
            if (交互音乐播放器.效果器.淡入淡出.签名到到挂载号.ContainsKey(淡入淡出.生成流与段落签名(数据.播放流组[流别名].流编号, 数据.播放流组[流别名].当前段落!.编号)))
            {
                Debug.Print("执行淡入淡出效果");
                淡入淡出.使用效果(音频缓冲区, 偏移值, 读取大小, 数据.播放流组[流别名].流编号, 数据.播放流组[流别名].当前段落!.编号, 已载文件组[当前文件].WaveFormat);
            }
        }


        public bool 命令_更换文件(string 文件别名, string 流别名, int 起始偏移)
        {
            //第一次更换文件，显示处理逻辑
            数据.播放流组[流别名].当前段落!.已加载一次 = true;
            if (数据.当前流 == null || 数据.空值或空引用(音乐播放数据.播放数据组.播放流组, 流别名))
            { Debug.Print("当前流是空的，无法判定更换哪个文件"); return false; }
            if (数据.播放流组[流别名].当前段落!.已加载一次 && 流别名 == 数据.当前流!.流别名)
            {
                if (数据.播放流组[流别名].当前段落!.绑定文件 == null) { return false; }
                var 当前播放文件别名 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
                //如果从开始播放完毕，则将设置偏移值 
                //BUG： 这个逻辑容易出现问题，应该点按使用点按的逻辑，这样强行相加可能出错（暂时没出错先这样用着）

                if (已载文件组[当前播放文件别名].Position == 已载文件组[当前播放文件别名].Length)
                {
                    数据.播放流组[流别名].播放器偏移 += 已载文件组[当前播放文件别名].Length;
                }
                else//如果从中间跳转，则将设置偏移值
                {
                    int Cue点大小 = (int)数据.播放流组[流别名].当前段落.已通过Cue点读取大小;
                    var 播放器实际读入大小 = 数据.播放流组[流别名].当前段落!.播放位置.已播放字节 + Cue点大小; //一旦已播放字节没更新的时候就会出现偏差
                    var 最终值 = 起始偏移 - 播放器实际读入大小;
                    数据.播放流组[流别名].播放器偏移 += -最终值;

                    //清零临时数据
                    数据.播放流组[流别名].当前段落.已通过Cue点读取大小 = 0;
                    数据.播放流组[流别名].当前段落.已通过Cue点读取部分字节 = false;
                }

            }

            //更换流与段落逻辑
            数据.播放流组[流别名].当前段落 = 数据.段落组[文件别名];

            var 新播放文件别名 = 数据.播放流组[流别名].当前段落!.绑定文件!.别名;
            if (起始偏移 != -1)
            {
                //if (已载文件组.Count - 1 < 脚本.状态_当前段落[流编号]) { return false; }
                已载文件组[新播放文件别名].Position = 起始偏移;
            }

            if (数据.播放流组[流别名].当前段落!.循环等待 != false)
            {
                数据.播放流组[流别名].当前段落!.循环等待数据!.当前等待状态 = 段落信息._循环等待数据.等待状态.正在等待;
                计划.全局事件.Add(计划.事件.切换至循环等待段落);
            }
            var 匹配按钮数 = 计划.全局按钮集.Values.Where(x => x.可用段落 == 数据.播放流组[流别名].当前段落!.别名).Count();
            if (计划.全局按钮集 != null && 计划.全局按钮集!.Count > 0 && 计划.全局按钮集!.FirstOrDefault().Value.按钮显示名称 != "无脚本" && 匹配按钮数 > 0)
            {
                计划.全局事件.Add(计划.事件.切换至可控制段落);
            }
            if (计划.全局事件.Contains(计划.事件.自动处理所有Cue点))
            {
                脚本命令.回执_取下个Cue点();
            }


            return true;
        }

        public bool 命令_重定位(long 字节位置, string 文件别名)
        {
            //可能有BUG
            //播放器显示数据更新
            var 当前文件别名 = 数据.当前流!.当前段落!.绑定文件!.别名;
            var 别名 = 数据.段落组[文件别名].别名;
            if (当前文件别名 == 别名) { Debug.Print("命令_重定位中的两个别名实际为一个，另一个是不需要的"); }
            数据.当前流!.播放器偏移 += 已载文件组[当前文件别名].Position - (long)字节位置;
            已载文件组[别名].Position = 字节位置;
            return true;
        }

        /// <summary>
        /// 当本段落读取完毕后的操作
        /// </summary>
        /// <param name="流别名">指定流别名</param>
        /// <exception cref="NotImplementedException"></exception>
        public void 本段落读取完毕(string 流别名)
        {
            if (数据.播放流组[流别名].当前段落 == null) { Debug.Print($"无法找到在流{流别名}中的当前段落"); return; }
            if (数据.播放流组[流别名].当前段落!.循环等待 == false && 数据.播放流组[流别名].当前段落!.Cue点集 == null)
            {
                默认更换文件逻辑(流别名); return;
            }
            bool 是否循环等待;
            if (数据.播放流组[流别名].当前段落!.循环等待 != false)
            {
                循环等待播完更换文件逻辑(流别名); return;
            }

            var List = 数据.播放流组[流别名].当前段落!.Cue点集;
            if (List != null && List.Count == 0) { Debug.Print("未读取到CUE点"); 默认更换文件逻辑(流别名); return; }
            var CUE点 = List.Where(x => x.Value.出点位置 == 数据.播放流组[流别名].当前段落!.播放位置.总字节).FirstOrDefault().Value;
            if (CUE点 == null) { Debug.Print("未找到切换点，将不更换音乐"); 音频播放流组[流别名].流.Stop(); 数据.播放流组[流别名].当前段落!.状态 = 段落信息.播放状态.停止; return; }
            命令_更换文件(CUE点.入点段落, 流别名, (int)CUE点.入点位置);
            Debug.Print($"通过脚本{CUE点.入点段落} > {CUE点.入点位置}");
        }

        /// <summary>
        /// 如果没有设置更换逻辑，就使用此默认逻辑
        /// </summary>
        /// <param name="流别名"></param>
        private void 默认更换文件逻辑(string 流别名)
        {
            if (数据.播放流组[流别名].当前段落 != null && 数据.播放流组[流别名].当前段落!.启用循环)
            {
                命令_更换文件(数据.播放流组[流别名].当前段落!.别名, 流别名, 0);
                if (!数据.播放流组[流别名].当前段落.禁用默认赋值当前段落)
                {
                    数据.当前段落 = 数据.播放流组[流别名].当前段落;
                }
                return;
            }
            if (脚本.段落名称 == null) { return; }
            var 当前文件序号 = 脚本.段落名称.IndexOf(数据.播放流组[流别名].当前段落!.绑定文件!.别名);
            if (当前文件序号 == -1) { Debug.Print("不能确定下一个文件需要播放哪个文件，播放已停止"); return; }
            if (当前文件序号 == 脚本.文件组.Count - 1) //若已经到达最后一个文件
            {
                if (!脚本.拥有配置文件)//没有配置文件，则默认是永续音乐，循环最后一个Loop
                {
                    命令_更换文件(数据.播放流组[流别名].当前段落!.别名, 流别名, 0);
                    数据.当前段落 = 数据.播放流组[流别名].当前段落;
                    return;
                }
                return;//通过脚本控制，没有其他处理就停止
            }
            if (当前文件序号 > 脚本.文件组.Count - 1) { return; }
            var 下一文件别名 = 脚本.段落名称[当前文件序号 + 1];
            数据.当前文件 = 数据.文件组[下一文件别名];
            if (数据.播放流组[流别名].当前段落.禁用默认切换逻辑) { return; } //如果脚本中禁用了默认切换逻辑，则在此不做切换

            命令_更换文件(下一文件别名, 流别名, 0);
        }

        /// <summary>
        /// 如果在设置了循环等待用户按键后继续，则使用此逻辑（即交互式无缝循环）
        /// </summary>
        /// <param name="流别名"></param>
        private void 循环等待播完更换文件逻辑(string 流别名)
        {
            if (数据.播放流组[流别名].当前段落!.循环等待数据!.当前等待状态 == 段落信息._循环等待数据.等待状态.正在等待)
            {
                命令_更换文件(数据.播放流组[流别名].当前段落!.绑定文件!.别名, 流别名, 0);
            }
            if (数据.播放流组[流别名].当前段落!.循环等待数据!.当前等待状态 == 段落信息._循环等待数据.等待状态.结束 ||
                数据.播放流组[流别名].当前段落!.循环等待数据!.当前等待状态 == 段落信息._循环等待数据.等待状态.脚本执行)
            {

                var List = 数据.播放流组[流别名].当前段落!.Cue点集;
                if (List == null || List.Count == 0) { Debug.Print("未读取到CUE点"); 默认更换文件逻辑(流别名); return; }
                var CUE点 = List.Where(x => x.Value.出点位置 == 数据.播放流组[流别名].当前段落!.播放位置.总字节).FirstOrDefault().Value;
                命令_更换文件(CUE点.入点段落, 流别名, (int)CUE点.入点位置);
                Debug.Print($"通过脚本{CUE点.入点段落} > {CUE点.入点位置}");
                脚本命令.回执_启用默认按钮();
            }
        }


        #endregion
        #region 外部控制命令

        public bool 命令_暂停(string 播放流别名)
        {
            用户消息队列.Add(new Action(() =>
            {

                音频播放流组[播放流别名].流.Pause();
                if (数据.播放流组.ContainsKey(播放流别名) && 数据.播放流组[播放流别名].当前段落 != null)
                {
                    数据.播放流组[播放流别名].当前段落!.状态 = 段落信息.播放状态.暂停;
               
                }

            }));
            return true;
        }

        public bool 命令_停止(string 播放流别名)
        {
            音频播放流组[播放流别名].流.Stop();
            音频播放流组[播放流别名].流.Dispose();
            音频播放流组.Remove(播放流别名);
            if (数据.播放流组.ContainsKey(播放流别名) && 数据.播放流组[播放流别名].当前段落 != null)
            {
                数据.播放流组[播放流别名].当前段落!.状态 = 段落信息.播放状态.停止;
            }
            return true;
        }

        public bool 命令_停止所有()
        {
            用户消息队列.Add(new Action(() =>
            {

                停止输出 = true;
                foreach (var 播放器 in 音频播放流组)
                {
                    播放器.Value.流.Stop();
                    播放器.Value.流.Dispose();
                }
                foreach (var 数据 in 数据.播放流组)
                {
                    if (数据.Value.当前段落 != null)
                    {
                        数据.Value.当前段落.状态 = 段落信息.播放状态.停止;
                    }
                }
                音频播放流组.Clear();
                GC.Collect();

            }));
            return true;
        }

        public bool 命令_暂停所有()
        {
            用户消息队列.Add(new Action(() =>
            {
                foreach (var 播放器 in 音频播放流组)
                {
                    播放器.Value.流.Pause();
                }
                foreach (var 数据 in 数据.播放流组)
                {
                    if (数据.Value.当前段落 != null)
                    {
                        数据.Value.当前段落.状态 = 段落信息.播放状态.暂停;
                        音频控制中间件.播放数据.当前流.当前段落.状态 = 段落信息.播放状态.暂停;
                    }
                }
            }));
            return true;
        }

        public bool 命令_继续所有()
        {
            
                if (音频播放流组.Count == 0) { }
            if (音频播放流组.FirstOrDefault().Value == null) { }
            if (音频播放流组.FirstOrDefault().Value.流 == null) {  }
            foreach (var 播放器 in 音频播放流组)
            {
                //此处try 此处判断播放器是否有播放文件的“播放器.Value.OutputWaveFormat”的判空（Null）处理失效
                //访问到Null则直接跳过该逻辑
                try
                {
                    if (播放器.Value.流.PlayState != PlayState.Playing)
                    {
                        播放器.Value.流.Play();
                        Task.Run(() => { 
                        
                            读取文件流(当前播放流名称);
                        });
                    }
                }
                catch
                {
                    continue;
                }
          
        }
            foreach (var 数据 in 数据.播放流组)
            {
                if (数据.Value.当前段落 != null)
                {
                    数据.Value.当前段落.状态 = 段落信息.播放状态.播放中;
                }
            }
                音频控制中间件.播放数据.当前流.当前段落.状态 = 段落信息.播放状态.播放中;
          
            return true;
        }


        public bool 命令_全部重定位(double 位置百分比)
        {
            用户消息队列.Add(new Action(() =>
            {
                //播放器显示数据更新
                var 当前文件别名 = 数据.当前流.当前段落!.绑定文件!.别名;
            数据.当前流!.播放器偏移 += 已载文件组[当前文件别名].Position - (long)((double)已载文件组[当前文件别名].Length * (位置百分比 / 100));
            foreach (var 文件 in 已载文件组)
            {
                文件.Value.Position = (long)((double)文件.Value.Length * (位置百分比 / 100));
            }
            }));
            return true;
        }


        #endregion
    }
}
